package com.sneakxy.mybatis.commons.repository.query.parser;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.StaticTextSqlNode;
import org.apache.ibatis.scripting.xmltags.TextSqlNode;
import org.apache.ibatis.scripting.xmltags.TrimSqlNode;
import org.apache.ibatis.scripting.xmltags.WhereSqlNode;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;

import com.sneakxy.mybatis.commons.orm.MybatisEntityManager;
import com.sneakxy.mybatis.commons.orm.generation.GenerationManager;
import com.sneakxy.mybatis.commons.orm.mapping.MybatisColumn;
import com.sneakxy.mybatis.commons.repository.config.MybatisCommonsConfiguration;
import com.sneakxy.mybatis.commons.utils.MybatisUtils;

/**
 * @author 潜行的青衣
 */
public class MybatisPartTree extends PartTree {

	public static final String GENERIC_NAME_PREFIX = "param";
	
	private MybatisEntityManager entityManager;
	
	/**
	 * @param entityManager
	 * @param source
	 */
	public MybatisPartTree(MybatisEntityManager entityManager, String source) {
		super(source, entityManager.getEntityInformation().getJavaType());
		this.entityManager = entityManager;
	}
	
	public SqlSource buildSqlSource(String source) {
		final GenerationManager generationManager = this.getEntityManager().getGenerationManager();
		final MybatisCommonsConfiguration config = this.getEntityManager().getConfiguration();
		final StaticTextSqlNode textSqlNode = new StaticTextSqlNode(source);
		final List<SqlNode> whereSqlNodes = new ArrayList<SqlNode>();
		final Iterator<OrPart> it = this.iterator();
		List<MybatisColumn> columns = null;
		Iterator<MybatisColumn> cit = null;
		MybatisColumn column = null;
		SqlNode andNode = null;
		String property = null;
		String overage = null;
		String path = null;
		OrPart orPart = null;
		Part part = null;
		int index = 1;
		while(it.hasNext()) {
			orPart = it.next();
			final List<SqlNode> orContents = new ArrayList<SqlNode>();
			final Iterator<Part> andit = orPart.iterator();
			while(andit.hasNext()) {
				part = andit.next();
				property = part.getProperty().getSegment();
				overage = "." + property;
				path = new StringBuilder().append(GENERIC_NAME_PREFIX).append(index).toString();
				columns = this.getEntityManager().getColumns(property, path);
				StringBuilder sqlBuilder = new StringBuilder();
				//复合字段
				if(columns.size() > 1) {
					cit = columns.iterator();
					while(cit.hasNext()) {
						column = cit.next();
						sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
						sqlBuilder.append("=#{").append(RegExUtils.removeFirst(column.getPropertyPath(), overage)).append("} ");
					}
					andNode = new StaticTextSqlNode(sqlBuilder.toString());
				} else {
					column = columns.get(0);
					final String dotProperty = RegExUtils.removeFirst(column.getPropertyPath(), overage);
					switch(part.getType()) {
						case NEGATING_SIMPLE_PROPERTY:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append("!=#{").append(dotProperty).append("} ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case BETWEEN:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(" BETWEEN #{").append(dotProperty).append("} ");
							sqlBuilder.append("AND #{").append(GENERIC_NAME_PREFIX).append(index + 1).append("}");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case IS_NOT_NULL:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn())).append(" IS NOT NULL ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case IS_NULL:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn())).append(" IS NULL ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case TRUE:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn())).append(" = true ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case FALSE:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn())).append(" = false ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case BEFORE:
						case LESS_THAN:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append("<#{").append(dotProperty).append("} ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case LESS_THAN_EQUAL:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append("<=#{").append(dotProperty).append("} ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case AFTER:
						case GREATER_THAN:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(">#{").append(dotProperty).append("} ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case GREATER_THAN_EQUAL:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(">=#{").append(dotProperty).append("} ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case NOT_LIKE:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(" NOT LIKE #{").append(dotProperty).append("} ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case LIKE:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(" LIKE #{").append(dotProperty).append("} ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
							break;
						case STARTING_WITH:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(" LIKE '${").append(dotProperty).append("}%' ");
							andNode = new TextSqlNode(sqlBuilder.toString());
							break;
						case ENDING_WITH:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(" LIKE '%${").append(dotProperty).append("}' ");
							andNode = new TextSqlNode(sqlBuilder.toString());
							break;
						case CONTAINING:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(" LIKE '%${").append(dotProperty).append("}%' ");
							andNode = new TextSqlNode(sqlBuilder.toString());
							break;
						case NOT_CONTAINING:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append(" NOT LIKE '%${").append(dotProperty).append("}%' ");
							andNode = new TextSqlNode(sqlBuilder.toString());
							break;
						case NOT_IN:
							List<SqlNode> notIn = new ArrayList<SqlNode>(); 
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn())).append(" NOT IN ");
							notIn.add(new StaticTextSqlNode(sqlBuilder.toString()));
							notIn.add(new ForEachSqlNode(
									this.getEntityManager().getConfiguration(), 
									new StaticTextSqlNode("#{"+ path + "_item}"), dotProperty, false, path + "_index", path + "_item", "(", ") ", ","));
							andNode = new MixedSqlNode(notIn);
							break;
						case IN:
							List<SqlNode> in = new ArrayList<SqlNode>(); 
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn())).append(" IN ");
							in.add(new StaticTextSqlNode(sqlBuilder.toString()));
							in.add(new ForEachSqlNode(
									this.getEntityManager().getConfiguration(), 
									new StaticTextSqlNode("#{"+ path + "_item}"), dotProperty, false, path + "_index", path + "_item", "(", ") ", ","));
							andNode = new MixedSqlNode(in);
							break;
						default:
							sqlBuilder.append("AND ").append(generationManager.formatColumnName(column.getColumn()));
							sqlBuilder.append("=#{").append(dotProperty).append("} ");
							andNode = new StaticTextSqlNode(sqlBuilder.toString());
					}
				}
				orContents.add(andNode);
				index += part.getNumberOfArguments();
			}
			whereSqlNodes.add(new TrimSqlNode(config, new MixedSqlNode(orContents), " OR (", "AND", ") ", ""));
		}
		
		WhereSqlNode whereSqlNode = new WhereSqlNode(this.getEntityManager().getConfiguration(), new MixedSqlNode(whereSqlNodes));
		List<SqlNode> contents = new ArrayList<SqlNode>();
		contents.add(textSqlNode);
		contents.add(whereSqlNode);
		String sort = this.buildSortSql();
		if(StringUtils.isNotBlank(sort)) {
			contents.add(new StaticTextSqlNode(sort));
		}
		MixedSqlNode sqlNode = new MixedSqlNode(contents);
		return new DynamicSqlSource(this.getEntityManager().getConfiguration(), sqlNode);
	}
	
	public String buildSortSql() {
		return MybatisUtils.buildSortSql(this.getSort(), getEntityManager().getConfiguration().getDialect());
	}

	public MybatisEntityManager getEntityManager() {
		return entityManager;
	}

}
